// Copyright Epic Games, Inc. All Rights Reserved.

#pragma once

#include <zencore/logbase.h>
#include <zencore/zencore.h>

#include <fmt/args.h>

#if ZEN_PLATFORM_WINDOWS
#	define ZEN_LOG_SECTION(Id) ZEN_DATA_SECTION(Id)
#	pragma section(".zlog$f", read)
#	pragma section(".zlog$l", read)
#	pragma section(".zlog$m", read)
#	pragma section(".zlog$s", read)
#	define ZEN_DECLARE_FUNCTION static constinit ZEN_LOG_SECTION(".zlog$f") char FuncName[] = __FUNCTION__;
#	define ZEN_LOG_FUNCNAME	 FuncName
#else
#	define ZEN_LOG_SECTION(Id)
#	define ZEN_DECLARE_FUNCTION
#	define ZEN_LOG_FUNCNAME static_cast<const char*>(__func__)
#endif

namespace zen::logging {

void InitializeLogging();
void ShutdownLogging();
bool EnableVTMode();
void FlushLogging();

LoggerRef Default();
void	  SetDefault(std::string_view NewDefaultLoggerId);
LoggerRef ConsoleLog();
void	  SuppressConsoleLog();
LoggerRef ErrorLog();
void	  SetErrorLog(std::string_view LoggerId);
LoggerRef Get(std::string_view Name);

void ConfigureLogLevels(level::LogLevel Level, std::string_view Loggers);
void RefreshLogLevels();
void RefreshLogLevels(level::LogLevel DefaultLevel);

struct LogCategory
{
	inline LogCategory(std::string_view InCategory) : CategoryName(InCategory) {}

	inline zen::LoggerRef Logger()
	{
		if (LoggerRef)
		{
			return LoggerRef;
		}

		LoggerRef = zen::logging::Get(CategoryName);
		return LoggerRef;
	}

	std::string	   CategoryName;
	zen::LoggerRef LoggerRef;
};

void EmitConsoleLogMessage(int LogLevel, std::string_view Message);
void EmitConsoleLogMessage(int LogLevel, std::string_view Format, fmt::format_args Args);
void EmitLogMessage(LoggerRef& Logger, int LogLevel, std::string_view Message);
void EmitLogMessage(LoggerRef& Logger, const SourceLocation& Location, int LogLevel, std::string_view Message);
void EmitLogMessage(LoggerRef& Logger, int LogLevel, std::string_view Format, fmt::format_args Args);
void EmitLogMessage(LoggerRef& Logger, const SourceLocation& Location, int LogLevel, std::string_view Format, fmt::format_args Args);

template<typename... T>
auto
LogCaptureArguments(T&&... Args)
{
	return fmt::make_format_args(Args...);
}

}  // namespace zen::logging

namespace zen {

extern LoggerRef TheDefaultLogger;

inline LoggerRef
Log()
{
	if (TheDefaultLogger)
	{
		return TheDefaultLogger;
	}
	return zen::logging::ConsoleLog();
}

using logging::ConsoleLog;
using logging::ErrorLog;

}  // namespace zen

using zen::ConsoleLog;
using zen::ErrorLog;
using zen::Log;

inline consteval bool
LogIsErrorLevel(int LogLevel)
{
	return (LogLevel == zen::logging::level::Err || LogLevel == zen::logging::level::Critical);
};

#if ZEN_BUILD_DEBUG
#	define ZEN_CHECK_FORMAT_STRING(fmtstr, ...)      \
		while (false)                                 \
		{                                             \
			(void)fmt::format(fmtstr, ##__VA_ARGS__); \
		}
#else
#	define ZEN_CHECK_FORMAT_STRING(fmtstr, ...) \
		while (false)                            \
		{                                        \
		}
#endif

#define ZEN_LOG_WITH_LOCATION(InLogger, InLevel, fmtstr, ...)                                                                    \
	do                                                                                                                           \
	{                                                                                                                            \
		using namespace std::literals;                                                                                           \
		ZEN_DECLARE_FUNCTION                                                                                                     \
		static constinit ZEN_LOG_SECTION(".zlog$s") char						 FileName[]		= __FILE__;                      \
		static constinit ZEN_LOG_SECTION(".zlog$m") char						 FormatString[] = fmtstr;                        \
		static constinit ZEN_LOG_SECTION(".zlog$l") zen::logging::SourceLocation Location{FileName, __LINE__, ZEN_LOG_FUNCNAME}; \
		zen::LoggerRef															 Logger = InLogger;                              \
		ZEN_CHECK_FORMAT_STRING(fmtstr##sv, ##__VA_ARGS__);                                                                      \
		if (Logger.ShouldLog(InLevel))                                                                                           \
		{                                                                                                                        \
			zen::logging::EmitLogMessage(Logger,                                                                                 \
										 Location,                                                                               \
										 InLevel,                                                                                \
										 std::string_view(FormatString, sizeof FormatString - 1),                                \
										 zen::logging::LogCaptureArguments(__VA_ARGS__));                                        \
		}                                                                                                                        \
	} while (false);

#define ZEN_LOG(InLogger, InLevel, fmtstr, ...)                                                   \
	do                                                                                            \
	{                                                                                             \
		using namespace std::literals;                                                            \
		static constinit ZEN_LOG_SECTION(".zlog$m") char FormatString[] = fmtstr;                 \
		zen::LoggerRef									 Logger			= InLogger;               \
		ZEN_CHECK_FORMAT_STRING(fmtstr##sv, ##__VA_ARGS__);                                       \
		if (Logger.ShouldLog(InLevel))                                                            \
		{                                                                                         \
			zen::logging::EmitLogMessage(Logger,                                                  \
										 InLevel,                                                 \
										 std::string_view(FormatString, sizeof FormatString - 1), \
										 zen::logging::LogCaptureArguments(__VA_ARGS__));         \
		}                                                                                         \
	} while (false);

#define ZEN_DEFINE_LOG_CATEGORY_STATIC(Category, Name) \
	static zen::logging::LogCategory Category { Name }

#define ZEN_LOG_TRACE(Category, fmtstr, ...) ZEN_LOG(Category.Logger(), zen::logging::level::Trace, fmtstr, ##__VA_ARGS__)
#define ZEN_LOG_DEBUG(Category, fmtstr, ...) ZEN_LOG(Category.Logger(), zen::logging::level::Debug, fmtstr, ##__VA_ARGS__)
#define ZEN_LOG_INFO(Category, fmtstr, ...)	 ZEN_LOG(Category.Logger(), zen::logging::level::Info, fmtstr, ##__VA_ARGS__)
#define ZEN_LOG_WARN(Category, fmtstr, ...)	 ZEN_LOG(Category.Logger(), zen::logging::level::Warn, fmtstr, ##__VA_ARGS__)
#define ZEN_LOG_ERROR(Category, fmtstr, ...) ZEN_LOG_WITH_LOCATION(Category.Logger(), zen::logging::level::Err, fmtstr, ##__VA_ARGS__)
#define ZEN_LOG_CRITICAL(Category, fmtstr, ...) \
	ZEN_LOG_WITH_LOCATION(Category.Logger(), zen::logging::level::Critical, fmtstr, ##__VA_ARGS__)

#define ZEN_TRACE(fmtstr, ...)	  ZEN_LOG(Log(), zen::logging::level::Trace, fmtstr, ##__VA_ARGS__)
#define ZEN_DEBUG(fmtstr, ...)	  ZEN_LOG(Log(), zen::logging::level::Debug, fmtstr, ##__VA_ARGS__)
#define ZEN_INFO(fmtstr, ...)	  ZEN_LOG(Log(), zen::logging::level::Info, fmtstr, ##__VA_ARGS__)
#define ZEN_WARN(fmtstr, ...)	  ZEN_LOG(Log(), zen::logging::level::Warn, fmtstr, ##__VA_ARGS__)
#define ZEN_ERROR(fmtstr, ...)	  ZEN_LOG_WITH_LOCATION(Log(), zen::logging::level::Err, fmtstr, ##__VA_ARGS__)
#define ZEN_CRITICAL(fmtstr, ...) ZEN_LOG_WITH_LOCATION(Log(), zen::logging::level::Critical, fmtstr, ##__VA_ARGS__)

#define ZEN_CONSOLE_LOG(InLevel, fmtstr, ...)                                                                 \
	do                                                                                                        \
	{                                                                                                         \
		using namespace std::literals;                                                                        \
		ZEN_CHECK_FORMAT_STRING(fmtstr##sv, ##__VA_ARGS__);                                                   \
		zen::logging::EmitConsoleLogMessage(InLevel, fmtstr, zen::logging::LogCaptureArguments(__VA_ARGS__)); \
	} while (false)

#define ZEN_CONSOLE(fmtstr, ...)		  ZEN_CONSOLE_LOG(zen::logging::level::Info, fmtstr, ##__VA_ARGS__)
#define ZEN_CONSOLE_TRACE(fmtstr, ...)	  ZEN_CONSOLE_LOG(zen::logging::level::Trace, fmtstr, ##__VA_ARGS__)
#define ZEN_CONSOLE_DEBUG(fmtstr, ...)	  ZEN_CONSOLE_LOG(zen::logging::level::Debug, fmtstr, ##__VA_ARGS__)
#define ZEN_CONSOLE_INFO(fmtstr, ...)	  ZEN_CONSOLE_LOG(zen::logging::level::Info, fmtstr, ##__VA_ARGS__)
#define ZEN_CONSOLE_WARN(fmtstr, ...)	  ZEN_CONSOLE_LOG(zen::logging::level::Warn, fmtstr, ##__VA_ARGS__)
#define ZEN_CONSOLE_ERROR(fmtstr, ...)	  ZEN_CONSOLE_LOG(zen::logging::level::Err, fmtstr, ##__VA_ARGS__)
#define ZEN_CONSOLE_CRITICAL(fmtstr, ...) ZEN_CONSOLE_LOG(zen::logging::level::Critical, fmtstr, ##__VA_ARGS__)

//////////////////////////////////////////////////////////////////////////

namespace zen {

class StringBuilderBase;

/** Thread-local diagnostics scope stack

	Intended to be used in logging and such to provide context on demand.
	A subclass should do as little work as possible at construction time
	and ideally only do something when the Emit function is called.
  */
class ScopedActivityBase
{
public:
	ScopedActivityBase();
	virtual ~ScopedActivityBase();
	virtual void Emit(StringBuilderBase& Target) = 0;

	inline ScopedActivityBase* GetNext() { return m_NextScope; }

private:
	ScopedActivityBase* m_NextScope;
};

class ScopedActivityString : public ScopedActivityBase
{
public:
	inline ScopedActivityString(const std::string_view& Text) : m_Text{Text} {}
	virtual ~ScopedActivityString();
	virtual void Emit(StringBuilderBase& Target) override;

private:
	const std::string_view m_Text;
};

template<typename Lambda>
class ScopedLazyActivity : public ScopedActivityBase
{
public:
	ScopedLazyActivity(Lambda&& InFunc) : m_CapturedFunc(std::move(InFunc)) {}
	~ScopedLazyActivity() = default;

	virtual void Emit(StringBuilderBase& Target) override { m_CapturedFunc(Target); }

private:
	const Lambda m_CapturedFunc;
};

std::string_view EmitActivitiesForLogging(StringBuilderBase& OutString);

#define ZEN_LOG_SCOPE(...) ScopedLazyActivity $Activity##__LINE__([&](StringBuilderBase& Out) { Out << fmt::format(__VA_ARGS__); })

#define ZEN_SCOPED_WARN(fmtstr, ...)                                                   \
	do                                                                                 \
	{                                                                                  \
		ExtendableStringBuilder<256> ScopeString;                                      \
		const std::string_view		 Scopes = EmitActivitiesForLogging(ScopeString);   \
		ZEN_LOG(Log(), zen::logging::level::Warn, fmtstr "{}", ##__VA_ARGS__, Scopes); \
	} while (false)

#define ZEN_SCOPED_ERROR(fmtstr, ...)                                                               \
	do                                                                                              \
	{                                                                                               \
		ExtendableStringBuilder<256> ScopeString;                                                   \
		const std::string_view		 Scopes = EmitActivitiesForLogging(ScopeString);                \
		ZEN_LOG_WITH_LOCATION(Log(), zen::logging::level::Err, fmtstr "{}", ##__VA_ARGS__, Scopes); \
	} while (false)

#define ZEN_SCOPED_CRITICAL(fmtstr, ...)                                                                 \
	do                                                                                                   \
	{                                                                                                    \
		ExtendableStringBuilder<256> ScopeString;                                                        \
		const std::string_view		 Scopes = EmitActivitiesForLogging(ScopeString);                     \
		ZEN_LOG_WITH_LOCATION(Log(), zen::logging::level::Critical, fmtstr "{}", ##__VA_ARGS__, Scopes); \
	} while (false)

ScopedActivityBase* GetThreadActivity();

void logging_forcelink();  // internal

}  // namespace zen
